package org.scisaga.tetra.server;

import org.scisaga.tetra.core.*;

import java.lang.management.ManagementFactory;
import java.net.URL;
import java.net.URLConnection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
//import java.awt.event.WindowEvent;
//import java.awt.event.WindowListener;
import java.io.*;

import javax.swing.*;

import org.h2.tools.Server;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.sun.management.OperatingSystemMXBean;

import edu.jas.kern.ComputerThreads;

public class ComputeNode extends JFrame {
	private static final long serialVersionUID = 3519046489374665819L;

	// Define Port number
	final int MsgPort = 12357;
	final int DBPort = 12358;

	Server dbServer;
	String dbUrl;
	PORTListenThread portListener;
	String hostName = SysTest.getHostName();

	ConcurrentHashMap<String, EntiImpl> entityTable;
	ConcurrentHashMap<String, ProcImpl> procTable;
	final int MaxProcNum = 20;
	float rating;

	// UI Components
	private JTextArea textArea = new JTextArea();
	private String log = "";
	private String credit = "  Author: Shang Kun    Email: scisaga@gmail.com " +
			"\r\n  Toyhouse Lab@2010    Tsinghua University" +
			"\r\n  Last Update: 2010/5/16";

	private JButton statusButton;
	private JButton quitButton;
	private JButton contriButton;

	private TrayIcon trayIcon;

	private Messager mgr = new Messager(this);
	

	/**
	 * Construct the object including its GUI
	 */
	public ComputeNode() {

		/**
		 * Initialize GUI
		 */
		super("ComputeNode");

		entityTable = new ConcurrentHashMap<String, EntiImpl>();
		procTable = new ConcurrentHashMap<String, ProcImpl>();
		EntiImpl.mgr = mgr;
		ProcImpl.mgr = mgr;
		EvalP.mgr = mgr;
		GuardImpl.mgr = mgr;

		setTitle("Tetra Compute Node @" + hostName);
		setSize(400, 300);

		// Centralize the window
		Dimension them = Toolkit.getDefaultToolkit().getScreenSize();
		setLocation((int) (them.width - this.getWidth()) / 2,
				(int) (them.height - this.getHeight()) / 2);
		setResizable(false);

		// Set ICON
		ImageIcon ico;
		if(this.getClass().getResource("/leaf.png") == null){
			ico = new ImageIcon("images/leaf.png");
		} else ico = new ImageIcon(this.getClass().getResource("/leaf.png"));
		setIconImage(ico.getImage());

		// Title
		JPanel header = new JPanel();
		header.setLayout(new GridLayout(1, 2));
		JLabel title = new JLabel("Tetra Compute Node", SwingConstants.CENTER);
		title.setIcon(new ImageIcon(ico.getImage().getScaledInstance(32, 32,
				Image.SCALE_REPLICATE)));
		title.setFont(new java.awt.Font("", Font.BOLD, 14));
		header.add(title);
		JLabel credit = new JLabel("v0.1h Toyhouse Lab@2010",
				SwingConstants.CENTER);

		credit.setFont(new java.awt.Font("", Font.PLAIN, 12));
		header.add(credit);

		// textArea and scroll
		textArea.setEditable(false);
		textArea.setFont(new java.awt.Font("Courier New", Font.PLAIN, 12));
		JScrollPane scroll = new JScrollPane(textArea);
		scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

		// Set the controlButtons
		JPanel controlButtons = new JPanel();
		controlButtons.setLayout(new GridLayout());
		statusButton = new JButton("Status");
		statusButton.setMnemonic(KeyEvent.VK_S);
		controlButtons.add(statusButton);
		contriButton = new JButton("Check contribution");
		contriButton.setMnemonic(KeyEvent.VK_C);
		controlButtons.add(contriButton);
		quitButton = new JButton("Exit");
		quitButton.setMnemonic(KeyEvent.VK_X);
		controlButtons.add(quitButton);

		// Set Container
		Container cp = getContentPane();
		cp.setLayout(new BorderLayout());
		cp.add(header, BorderLayout.PAGE_START);
		cp.add(scroll, BorderLayout.CENTER);
		cp.add(controlButtons, BorderLayout.PAGE_END);

		/**
		 * Initialize Port Listener
		 */
		portListener = new PORTListenThread(MsgPort, this);
		portListener.start();

		/**
		 * Start H2 DB TCP Server
		 */
		String[] a = { "-tcpPort", String.valueOf(DBPort), "-tcpAllowOthers" };
		try {
			dbServer = Server.createTcpServer(a).start();
			dbUrl = getDBUrl()+"/";
			log("H2 DB TCP Server started on port 12358");
		} catch (SQLException e) {
			log("*ERROR* H2 DB TCP Server couldn't start");
			// e.printStackTrace();
		}

		/**
		 * Calculate Rating
		 */
		OperatingSystemMXBean osmb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
		rating = (float) (rating() * Math.sqrt(osmb.getAvailableProcessors()));
		
		/**
		 * Add Event Listeners
		 */
		statusButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				if (statusButton.getText().equals("Status")) {
					statusButton.setText("Log");
					statusButton.setMnemonic(KeyEvent.VK_G);
					textArea.setText(getStatus(true));
				} else {
					statusButton.setText("Status");
					statusButton.setMnemonic(KeyEvent.VK_S);
					textArea.setText(log);
				}
			}
		});

		ActionListener checkListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					java.awt.Desktop.getDesktop().browse(
							new java.net.URI("http://toyhouse.cc/tetra/check_contribution.php?nodeName=" + hostName));
				} catch (Exception ex) {
				}
			}
		};
		contriButton.addActionListener(checkListener);

		ActionListener exitListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				unloadAllProc();
				ComputerThreads.terminate();
				printToFile(textArea.getText());
				
				// TODO: should log out from server
				setVisible(false);
				dispose();
				System.exit(0);
			}
		};

		quitButton.addActionListener(exitListener);
		
		setDefaultCloseOperation(HIDE_ON_CLOSE);
		
		/* unused windows listener
		addWindowListener(new WindowListener() {
			@Override
			public void windowActivated(WindowEvent arg0) {
			}

			@Override
			public void windowClosed(WindowEvent arg0) {
			}

			@Override
			public void windowClosing(WindowEvent arg0) {
				stopAllPoc(procTable);
				printToFile(textArea.getText());
				setVisible(false);
				dispose();
				System.exit(0);
			}

			@Override
			public void windowDeactivated(WindowEvent arg0) {
			}

			@Override
			public void windowDeiconified(WindowEvent arg0) {
			}

			@Override
			public void windowIconified(WindowEvent arg0) {
			}

			@Override
			public void windowOpened(WindowEvent arg0) {
			}
		});
		*/

		/**
		 * System Tray
		 */
		if (SystemTray.isSupported()) {

			SystemTray tray = SystemTray.getSystemTray();
			Image image;
			if(this.getClass().getResource("/leaf.png") == null){
				image = Toolkit.getDefaultToolkit().getImage("images/leaf.png");
			} else image = Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("/leaf.png"));
			
			MouseListener mouseListener = new MouseListener() {

				@Override
				public void mouseClicked(MouseEvent e) {
					setVisible(true);
					toFront();
				}

				@Override
				public void mouseEntered(MouseEvent arg0) {

				}

				@Override
				public void mouseExited(MouseEvent arg0) {

				}

				@Override
				public void mousePressed(MouseEvent arg0) {

				}

				@Override
				public void mouseReleased(MouseEvent arg0) {

				}
			};

			PopupMenu popup = new PopupMenu();

			ActionListener homepageListener = new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					try {
						java.awt.Desktop.getDesktop().browse(
								new java.net.URI("http://toyhouse.cc/tetra/"));
					} catch (Exception ex) {
					}
				}
			};

			MenuItem report_bug = new MenuItem("About");
			report_bug.addActionListener(homepageListener);
			popup.add(report_bug);

			MenuItem contribution = new MenuItem("Check Contribution");
			contribution.addActionListener(checkListener);
			popup.add(contribution);

			popup.addSeparator();

			MenuItem exit = new MenuItem("Exit");
			exit.addActionListener(exitListener);
			popup.add(exit);

			// About trayIcon
			trayIcon = new TrayIcon(image, "Tetra Compute Node @"+hostName, popup);

			ActionListener actionListener = new ActionListener() {
				// Display current compute node status
				public void actionPerformed(ActionEvent e) {
					trayIcon.displayMessage("Tetra Compute Node @"+hostName,
							getStatus(false), TrayIcon.MessageType.INFO);
				}
			};

			trayIcon.setImageAutoSize(true);
			trayIcon.addActionListener(actionListener);
			trayIcon.addMouseListener(mouseListener);

			try {
				tray.add(trayIcon);
			} catch (AWTException e) {
				log("TrayIcon could not be added.");
			}

		} else {
			// System Tray is not supported
		}
		// pack();
	}

	/**
	 * Main()
	 * 
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] argv) throws InterruptedException {
		ComputeNode cn = new ComputeNode();
		cn.setVisible(true);
		cn.log("Started, Port Listening 12357,12358");
		String param = "";
		param += SysTest.getHostIp();
		param += "," + cn.hostName;
		param += "," + cn.dbUrl;
		param += "," + cn.rating;
		URL url;
		try {
			url = new URL("http://toyhouse.cc/tetra/reg.php?token=" + param);
			URLConnection urlconn = url.openConnection();
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					urlconn.getInputStream()));
			cn.log(reader.readLine());
			reader.close();
		} catch (Exception e) {
			e.printStackTrace();
			cn.setVisible(true);
			cn.toFront();
			cn.log("*ERROR* Couldn't register on NameServer");
			cn.log("*ERROR* Test your Internet Connection");
			cn.log("*ERROR* Tetra Compute Node exit in 3 seconds");
			ComputerThreads.terminate();
			Thread.sleep(3000);
			System.exit(0);
			// e.printStackTrace();
		}

		ComputerThreads.terminate();
	}

	public void log(String s) {
		String time = new SimpleDateFormat("[HH:mm:ss] ").format(new Date());
		if (statusButton.getText().equals("Log")) {
			statusButton.setText("Status");
			statusButton.setMnemonic(KeyEvent.VK_S);
			textArea.setText(log);
		}
		this.textArea.append(time + s + "\r\n");
		log += time + s + "\r\n";
		if(log.length()>16384){
			printToFile(log);
			this.textArea.setText("");
			log = "";
		}
	}

	protected boolean loadEntity(JSONObject json) {
		try {
			if (entityTable.containsKey(json.getString("id"))) {
				log("loadEntity() Entity already exist");
				return false;
			} else {
				EntiImpl e = new EntiImpl(json);
				if (!e.tryDB()) {
					log("*Error* loadEntity() -> Database Connection Error : "
							+ e.getDBUrl());
					return false;
				}
				if (!e.reg()) {
					log("*Error* loadEntity() -> Can't create table : "
							+ e.getName() + "@" + e.getDBUrl());
					return false;
				}
				if (e.initSuccess()) {
					entityTable.put(e.getId(), e);
					log("Entity [" + e.getId() + "] loaded");
					return true;
				} else
					return false;
			}
		} catch (JSONException e) {
			// e.printStackTrace();
			return false;
		}
	}
	
	@SuppressWarnings("unchecked")
	protected boolean putTokens(String id, JSONArray tokens){
		try {
			boolean flag = true;
			int tokenNum = 0;
			for (int i = 0; i < tokens.length(); i++){
				HashMap<String,String> aToken = new HashMap<String,String>();
				String key = null;
				JSONObject aTokenJson = tokens.getJSONObject(i);
				for(java.util.Iterator<String> iter = aTokenJson.keys(); iter.hasNext(); ){
					key = iter.next();
					aToken.put(key,aTokenJson.getString(key));
				}
				flag = flag && entityTable.get(id).putToken(aToken);
				tokenNum ++;
			}
			if(flag){ 
				log("Entity "+id+" putTokens ["+tokenNum+"] successfully");
				return true;
			} else {
				log("*ERROR* Entity "+id+" putTokens (SQL ERROR)");
				return false;
			}
		} catch (Exception e) {
			log("*ERROR* Entity " + id + " putTokens");
			return false;
		}
	}
	
	protected String getAllTokens(String task_id, String id){
		JSONObject response = new JSONObject();
		try {
			response.put("id", task_id);
			response.put("Tokens", entityTable.get(id).getAllTokens());
		} catch (Exception e) {
			log("*ERROR* getAllTokes()");
			e.printStackTrace();
		}
		return response.toString();
	}
	
	protected String getTokenNum(String task_id, String id){
		JSONObject response = new JSONObject();
		try {
			response.put("id", task_id);
			response.put("TokenNum", entityTable.get(id).getTokenNum());
		} catch (Exception e) {
			log("*ERROR* getTokenNum()");
			e.printStackTrace();
		}
		return response.toString();
	}
		
	protected boolean unloadEntity(String id) {
		try {
			EntiImpl e = entityTable.get(id);
			entityTable.remove(id);
			e.close();
			log("Entity [" + id + "] unloaded");
		} catch (Exception e) {
			log("*ERROR* unloadEntity() -> No such Entity " + id);
			return false;
		}
		return true;
	}
	
	protected boolean unloadAllEntities(){
		try {
			for(String id : entityTable.keySet()){
				entityTable.get(id).close();
				entityTable.remove(id);
			}
			log("Entities all unloaded");
		} catch (Exception e) {
			log("*ERROR* unloadAllEntities()");
			return false;
		}
		return true;
	}

	protected boolean loadProc(JSONObject json) {
		if (procTable.size() >= MaxProcNum) {
			log("loadProc() Can't exceeds MaxProcNum");
			return false;
		} else {
			try {
				if (procTable.containsKey(json.getString("id"))) {
					log("*ERROR* loadProc() -> Process already exist");
					return false;
				} else {
					ProcImpl p = new ProcImpl(json, this.entityTable);
					if (p.initSuccess()) {
						procTable.put(p.get_Id(), p);
						log("Process [" + p.get_Id() + "] loaded");
						return true;
					} else
						return false;
				}
			} catch (Exception e) {
				e.printStackTrace();
				log("*ERROR* loadProc()");
				return false;
			}
		}
	}

	protected boolean unloadProc(String id) {
		try {
			ProcImpl p = procTable.get(id);
			p.setDone();
			procTable.remove(id);
			log("Process [" + id + "] unloaded");
		} catch (Exception e) {
			log("*ERROR* unloadProc() error or Process may not exist ");
			return false;
		}
		return true;
	}

	protected boolean runProc(String id) {
		try {
			ProcImpl p = procTable.get(id);

			if(p.isAlive()){
				try {
					log("Process [" + id + "] already started");
				} catch (Exception e) {
					e.printStackTrace();
					log("*ERROR* runProc() Process notify exception");
					return false;
				}
			}else{
				try {
					p.start();
					log("Process [" + id + "] started");
				} catch (Exception e) {
					e.printStackTrace();
					log("*ERROR* runProc() Process start exception");
					return false;
				}
			}
			
		} catch (Exception e) {
			//e.printStackTrace();
			log("*ERROR* runProc() Process may not exist");
			return false;
		}
		return true;
	}

	protected boolean setProcPriority(String id, int i) {
		try {
			procTable.get(id).setPriority(i);
			log("Process [" + id + "] setPriority(" + i + ")");
		} catch (Exception e) {
			log("*ERROR* setPriority() Process may not exist");
			return false;
		}
		return true;
	}

	protected boolean unloadAllProc() {
		try {
			for (String key : procTable.keySet()) {
				if (procTable.get(key).isAlive()){
					procTable.get(key).setDone();
					procTable.remove(key);
				}
			}
			return true;
		} catch (Exception ex) {
			return false;
		}
	}

	protected boolean ifDBRunning() {
		if (dbServer.getStatus().split(" ")[2] == "running") {
			return true;
		} else
			return false;
	}

	protected String getDBUrl() {
		return dbServer.getURL();
	}

	protected JSONObject getHostEntity() {
		JSONObject hostEntity = new JSONObject();
		for(String key : this.entityTable.keySet()){
			try {
				hostEntity.put(key, this.entityTable.get(key).getTokenNum());
			} catch (JSONException e) {
				e.printStackTrace();
			}
		}
		return hostEntity;
	}

	protected JSONObject getHostProc() {
		HashMap<String, String> procStat = new HashMap<String, String>();
		for (String key : this.procTable.keySet()) {
			procStat.put(key, this.procTable.get(key).status);
		}

		return new JSONObject(procStat);
	}
	
	protected String getStatus(boolean b){
		String s = "";
		s += "DBUrl: " + dbServer.getURL();
		s += "\r\nHostEntity ->";
		for(String key : entityTable.keySet()){
			s += "\r\n    " + key + " : " + entityTable.get(key).getTokenNum();
		}
		s += "\r\nHostProcess ->";
		for(String key : procTable.keySet()){
			s += "\r\n    " + key + " : " + procTable.get(key).status;
		}
		if(b) s += "\r\n\r\nRating: " + rating + "\r\n\r\n" + credit;
		return s;
	}
	
	static protected float rating(){
		EvalJ p = new EvalJ("{\"eval\":[\"$y=Sin[N[$x]]\"]}");
		
		long startTime = System.currentTimeMillis();
		
		for(int i=0;i<1000;i++){
			p.load("{\"$x\":\""+i+"\"}");
			if(true){
				p.gen("{\"varOut\":[\"$y\"]}");
			}
		}
		
		return (float) 1000/((System.currentTimeMillis() - startTime));
	}
	
	/**
	 * Save outputs to file
	 * 
	 * @param s
	 */
	protected static void printToFile(String s) {

		File log = new File("log");
		if (!log.exists())
			log.mkdir();
		String fileName = new SimpleDateFormat("[yyyy-MM-dd] HH-mm-ss")
				.format(new Date());
		try {
			FileOutputStream out = new FileOutputStream("log/" + fileName
					+ ".txt");
			PrintStream ps = new PrintStream(out);
			ps.print(s);

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}
